package networkInterface;

import hci.ChatGui;

import java.io.File;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;

import observerNotification.ErrorObserverNotification;
import observerNotification.ObserverNotification;
import main.ChatSystem;
import model.Datagram;
import controller.AbstractController;
import cs.remoteapp.Goodbye;
import cs.remoteapp.Hello;
import cs.remoteapp.Message;


/**
 * Network interface management class
 * 
 * <p>
 * <ul>
 * <li>Project: 	ChatSystemG3</li>
 * <li>Called by: 	ViewController</li>
 * </ul>
 * </p>
 * 
 * @author COMBES Nicolas - LECHANTRE Arnaud
 * @see ObserverNotification
 */
public class ChatNI implements FromNetwork, ToNetwork{
	
	/**
	 * app message sender
	 */
	private ChatNIMessageSender messageSender;								
	/**
	 * app message receiver
	 */
	private ChatNIMessageReceiver messageReceiver;					
	/**
	 * app controller
	 */
	private AbstractController controller;							
	
	/**
	 * self-explanatory
	 */
	private MessageFactory messageFactory;
	
	/**
	 * app file message sender
	 */
	private ArrayList<ChatNIFileSender> fileSender;
	
	/**
	 * app file message receiver
	 */
	private ArrayList<ChatNIFileReceiver> fileReceiver;
	
	
	/**
	 * Construct
	 * @param control
	 * @throws Exception in case connexion failed
	 */
	public ChatNI(AbstractController control) throws Exception {
		super();
		this.controller=control;
		//this.localUserName=name;
		//this.fileSender = new ChatNIFileSender();						//Deleted to make a thread
		try {
			this.messageReceiver = new ChatNIMessageReceiver(this);
			this.messageSender = new ChatNIMessageSender();
			this.messageFactory=new MessageFactory();
			this.fileReceiver= new ArrayList<ChatNIFileReceiver>();
			this.fileSender = new ArrayList<ChatNIFileSender>();
			new Thread(this.messageReceiver).start();
		} catch (SocketException e) {
			this.relayError(new ErrorObserverNotification(ChatGui.strings.getString("errorSocketAlreadyOpenedText")));
			throw ChatNIConnectionException();
		}
	}
	

	/**
	 * giveBCast; Contains two modes, one using 255.255.255.255 as bcast address, the other being a real bcast adress reserch method valid only on local networks in tp rooms.
	 * @return the broadcast address of the local network.
	 */
	private InetAddress giveBCast() {
		Boolean mode = false;		//Boolean used because inetAddress.getBroacast do no function everywhere
		if (mode) {
			Enumeration<NetworkInterface> interfaces;
			try {
				//We get the enumeration of all the network interfaces
				interfaces = NetworkInterface.getNetworkInterfaces();
				
				//Then we iterate for each interface
				while (interfaces.hasMoreElements()) {
					//we get the interface
					NetworkInterface networkInterface = interfaces.nextElement(); 
					for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
						InetAddress broadcast = interfaceAddress.getBroadcast();
						if (broadcast == null) {
							if (ChatSystem.debugMode) System.out.println("networkInterface.ChatNI: BCast is null.");
						}
						else {
							return broadcast;//crade, un while n'as jamais tu� personne -_-
						}
					}
					//continue;
				}
				
			} catch (SocketException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			}
		else {
			//If we are on a network where some functions do not work...
			try {
				return InetAddress.getByName("255.255.255.255");
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				if (ChatSystem.debugMode) System.out.println("networkInterface.ChatNI: Caught exception when sending to 255.255.255.255");
			}
		}
		return null;
	}
	
	/**
	 * Sends an Hello message containing local user name
	 * @param username name of the local user
	 */
	public void broadcastHello(String username) {
		if (ChatSystem.debugMode) {System.out.println("networkInterface.ChatNI: BCast is "+this.giveBCast().toString()+".");}
		Message hi= this.messageFactory.getNewMessage(MessageFactory.enum_type_message.HELLO, username, true);
		this.messageSender.sendDatagram(hi, this.giveBCast());
	}

	

	/*
	 * Overriding methods of FromNetwork et ToNetwork 
	 */
	@Override
	public void receiveUDPDatagram(Datagram d) {
		this.controller.setMessageReceived(d);
	}

	@Override
	public void receiveTCPPacket( int convId, File directory) {
		ChatNIFileReceiver cr= new ChatNIFileReceiver(this, ChatSystem.tcpPort, directory, convId);
		this.fileReceiver.add(cr);
		new Thread(cr).start();
	}

	@Override
	public void sendHello(String localUsername, InetAddress remoteAddress) {
		if (ChatSystem.debugMode) System.out.println("networkInterface.ChatNI: "+ localUsername+"@localhost says hello to " + remoteAddress.getHostAddress().toString());
		this.messageSender.sendDatagram(this.messageFactory.getNewMessage(MessageFactory.enum_type_message.HELLO, localUsername, false), remoteAddress);
	}

	
	/**
	 * Aimed at sending a broadcasted Goodbye message
	 * @param localUsername name of local user
	 */
	@Override
	public void sendGoodbye(String localUsername) {
		if(this.messageSender!=null){
			if (ChatSystem.debugMode) System.out.println("networkInterface.ChatNI: Broadcast is "+this.giveBCast().toString()+".");
			this.messageSender.sendDatagram(this.messageFactory.getNewMessage(MessageFactory.enum_type_message.GOODBYE, localUsername, false), this.giveBCast());
			this.messageReceiver.close();
			this.messageSender.close();
			for(int i=0; i<this.fileReceiver.size();i++){
				this.fileReceiver.get(i).close();
			}
			for(int i=0; i<this.fileSender.size();i++){
				this.fileSender.get(i).close();
			}
		}
		
	}

	
	/**
	 * Sends a text message to remoteAddress
	 * @param localUsername name of the local user
	 * @param messageTosend message to send to the remote user
	 * @param remoteAddress address of the remote user
	 */
	@Override
	public void sendMessage(String username, String messageToSend, InetAddress remoteAddress) {
		if (ChatSystem.debugMode){
			System.out.println("networkInterface.ChatNI:  sendMessage :[" + username + "@" + 
					remoteAddress.getHostAddress().toString() + "] sent a text message: " + messageToSend);
		}
		this.messageSender.sendDatagram(this.messageFactory.getNewMessage(MessageFactory.enum_type_message.TEXT, username, messageToSend), remoteAddress);
	}

	@Override
	public void sendFile(int convId, String localUsername, File file, InetAddress remoteAddress) {
		if (ChatSystem.debugMode) System.out.println("networkInterface.ChatNI: sendFile :" + file.getName());
		/*Must create a FileSender thread*/
		ChatNIFileSender fs = new ChatNIFileSender(this.messageFactory.getNewMessage(MessageFactory.enum_type_message.FILE, localUsername, file), remoteAddress, ChatSystem.tcpPort, this, convId);
		this.fileSender.add(fs);
		new Thread(fs).start();
	}

	@Override
	public void sendFileRequest(String localUsername, File file, InetAddress remoteAddress) {
		if (ChatSystem.debugMode){
			System.out.println("networkInterface.ChatNI: sendFileRequest :" + file.getName());
		}
		this.messageSender.sendDatagram(this.messageFactory.getNewMessage(MessageFactory.enum_type_message.FILEREQUEST, localUsername, file.getName(), file.length()), remoteAddress);
	}

	@Override
	public void sendFileAnswer(int convId, String localUsername, InetAddress remoteAddress,  String fileName, File downloadDirectory) {
		if (ChatSystem.debugMode){
			System.out.println("networkInterface.ChatNI: sendFileAnswer :" + fileName + " " +downloadDirectory!=null);
		}
		this.receiveTCPPacket(convId, downloadDirectory);
		//and send the response to the network
		this.messageSender.sendDatagram(this.messageFactory.getNewMessage(MessageFactory.enum_type_message.FILEANSWER, localUsername, fileName, downloadDirectory!=null), remoteAddress);
	}
	

	@Override
	public void notifyFileReceived(ChatNIFileReceiver chatNIFileReceiver,int convId) {
		this.controller.notifyFileReceived(convId);
		this.fileReceiver.remove(chatNIFileReceiver);
	}

	@Override
	public void notifyFileReceptionAbort(ChatNIFileReceiver chatNIFileReceiver,int convId) {
		this.controller.notifyFileReceptionAbort(convId);
		this.fileReceiver.remove(chatNIFileReceiver);
	}

	@Override
	public void notifyFileTransferFinished(ChatNIFileSender chatNIFileSender,int convId) {
		this.controller.notifyFileSent(convId);
		this.fileSender.remove(chatNIFileSender);
	}
	
	@Override
	public void notifyFileEmissionAbort(ChatNIFileSender chatNIFileSender,int convId) {
		this.controller.notifyFileReceptionAbort(convId);
		this.fileSender.remove(chatNIFileSender);
	}
	
	@Override
	public void relayError(ErrorObserverNotification error) {
		this.controller.relayError(error);
	}

	private Exception ChatNIConnectionException() {
		return new Exception("errorSocketAlreadyOpenedText");
	}


	
	/*--------------------------------------------------------------------------
	 * 							DEPRECATED FUNCTIONS
	 *-------------------------------------------------------------------------*/
	/**
	 * Sends an hello datagram to specified host
	 * @param username
	 * @param remoteAddress
	 * @deprecated
	 */
	public void sayHello(String username, InetAddress remoteAddress) {
		if (ChatSystem.debugMode) {System.out.println("networkInterface.ChatNI: "+ username+" @ " + remoteAddress.getHostAddress().toString()+" says hello.");}
		this.messageSender.sendDatagram(new Hello(username), remoteAddress);
	}
	
	/**
	 * Sends a Goodbye message in bcast
	 * @param username
	 * @deprecated
	 */
	public void boadcastBye(String username) {
		if (ChatSystem.debugMode) System.out.println("networkInterface.ChatNI: BCast is "+this.giveBCast().toString()+".");
		/*Sender sends a common.Goodbye object to the bcast address*/
		this.messageSender.sendDatagram(new Goodbye(username), this.giveBCast());
		this.messageReceiver.close();
	}
}
